/******************************************************************************* * Signavio Core Components * Copyright (C) 2012 Signavio GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package de.hpi.bpmn2_0.validation; import java.util.HashMap; import java.util.List; import de.hpi.bpmn2_0.model.BaseElement; import de.hpi.bpmn2_0.model.Collaboration; import de.hpi.bpmn2_0.model.Definitions; import de.hpi.bpmn2_0.model.FlowElement; import de.hpi.bpmn2_0.model.FlowNode; import de.hpi.bpmn2_0.model.Process; import de.hpi.bpmn2_0.model.activity.Activity; import de.hpi.bpmn2_0.model.activity.SubProcess; import de.hpi.bpmn2_0.model.activity.type.ReceiveTask; import de.hpi.bpmn2_0.model.bpmndi.BPMNEdge; import de.hpi.bpmn2_0.model.bpmndi.BPMNShape; import de.hpi.bpmn2_0.model.bpmndi.di.DiagramElement; import de.hpi.bpmn2_0.model.choreography.Choreography; import de.hpi.bpmn2_0.model.choreography.ChoreographyActivity; import de.hpi.bpmn2_0.model.connector.DataInputAssociation; import de.hpi.bpmn2_0.model.connector.DataOutputAssociation; import de.hpi.bpmn2_0.model.connector.Edge; import de.hpi.bpmn2_0.model.connector.MessageFlow; import de.hpi.bpmn2_0.model.connector.SequenceFlow; import de.hpi.bpmn2_0.model.data_object.DataInput; import de.hpi.bpmn2_0.model.data_object.DataOutput; import de.hpi.bpmn2_0.model.data_object.Message; import de.hpi.bpmn2_0.model.event.BoundaryEvent; import de.hpi.bpmn2_0.model.event.CompensateEventDefinition; import de.hpi.bpmn2_0.model.event.ConditionalEventDefinition; import de.hpi.bpmn2_0.model.event.EndEvent; import de.hpi.bpmn2_0.model.event.Event; import de.hpi.bpmn2_0.model.event.IntermediateCatchEvent; import de.hpi.bpmn2_0.model.event.MessageEventDefinition; import de.hpi.bpmn2_0.model.event.SignalEventDefinition; import de.hpi.bpmn2_0.model.event.StartEvent; import de.hpi.bpmn2_0.model.event.TimerEventDefinition; import de.hpi.bpmn2_0.model.gateway.EventBasedGateway; import de.hpi.bpmn2_0.model.gateway.Gateway; import de.hpi.bpmn2_0.model.gateway.GatewayDirection; import de.hpi.bpmn2_0.model.participant.Lane; import de.hpi.bpmn2_0.model.participant.Participant; import de.hpi.diagram.verification.AbstractSyntaxChecker; public class BPMN2SyntaxChecker extends AbstractSyntaxChecker { protected static final String NO_SOURCE = "BPMN_NO_SOURCE"; protected static final String NO_TARGET = "BPMN_NO_TARGET"; protected static final String MESSAGE_FLOW_NOT_CONNECTED = "BPMN_MESSAGE_FLOW_NOT_CONNECTED"; protected static final String DIFFERENT_PROCESS = "BPMN_DIFFERENT_PROCESS"; protected static final String SAME_PROCESS = "BPMN_SAME_PROCESS"; protected static final String FLOWOBJECT_NOT_CONTAINED_IN_PROCESS = "BPMN_FLOWOBJECT_NOT_CONTAINED_IN_PROCESS"; protected static final String ENDEVENT_WITHOUT_INCOMING_CONTROL_FLOW = "BPMN_ENDEVENT_WITHOUT_INCOMING_CONTROL_FLOW"; protected static final String STARTEVENT_WITHOUT_OUTGOING_CONTROL_FLOW = "BPMN_STARTEVENT_WITHOUT_OUTGOING_CONTROL_FLOW"; // protected static final String // INTERMEDIATEEVENT_WITHOUT_INCOMING_CONTROL_FLOW = // "BPMN_INTERMEDIATEEVENT_WITHOUT_INCOMING_CONTROL_FLOW"; protected static final String STARTEVENT_WITH_INCOMING_CONTROL_FLOW = "BPMN_STARTEVENT_WITH_INCOMING_CONTROL_FLOW"; protected static final String ATTACHEDINTERMEDIATEEVENT_WITH_INCOMING_CONTROL_FLOW = "BPMN_ATTACHEDINTERMEDIATEEVENT_WITH_INCOMING_CONTROL_FLOW"; protected static final String ATTACHEDINTERMEDIATEEVENT_WITHOUT_OUTGOING_CONTROL_FLOW = "BPMN_ATTACHEDINTERMEDIATEEVENT_WITHOUT_OUTGOING_CONTROL_FLOW"; protected static final String ENDEVENT_WITH_OUTGOING_CONTROL_FLOW = "BPMN_ENDEVENT_WITH_OUTGOING_CONTROL_FLOW"; protected static final String EVENTBASEDGATEWAY_BADCONTINUATION = "BPMN_EVENTBASEDGATEWAY_BADCONTINUATION"; protected static final String NODE_NOT_ALLOWED = "BPMN_NODE_NOT_ALLOWED"; protected static final String MESSAGE_FLOW_NOT_ALLOWED = "BPMN_MESSAGE_FLOW_NOT_ALLOWED"; // BPMN 2.0 Specific protected static final String DATA_INPUT_WITH_INCOMING_DATA_ASSOCIATION = "BPMN2_DATA_INPUT_WITH_INCOMING_DATA_ASSOCIATION"; protected static final String DATA_OUTPUT_WITH_OUTGOING_DATA_ASSOCIATION = "BPMN2_DATA_OUTPUT_WITH_OUTGOING_DATA_ASSOCIATION"; protected static final String EVENT_BASED_WITH_TOO_LESS_OUTGOING_SEQUENCE_FLOWS = "BPMN2_EVENT_BASED_WITH_TOO_LESS_OUTGOING_SEQUENCE_FLOWS"; protected static final String EVENT_BASED_TARGET_WITH_TOO_MANY_INCOMING_SEQUENCE_FLOWS = "BPMN2_EVENT_BASED_TARGET_WITH_TOO_MANY_INCOMING_SEQUENCE_FLOWS"; protected static final String EVENT_BASED_EVENT_TARGET_CONTRADICTION = "BPMN2_EVENT_BASED_EVENT_TARGET_CONTRADICTION"; protected static final String EVENT_BASED_WRONG_TRIGGER = "BPMN2_EVENT_BASED_WRONG_TRIGGER"; protected static final String EVENT_BASED_WRONG_CONDITION_EXPRESSION = "BPMN2_EVENT_BASED_WRONG_CONDITION_EXPRESSION"; protected static final String EVENT_BASED_NOT_INSTANTIATING = "BPMN2_EVENT_BASED_NOT_INSTANTIATING"; protected static final String EVENT_BASED_WITH_TOO_LESS_INCOMING_SEQUENCE_FLOWS = "BPMN2_EVENT_BASED_WITH_TOO_LESS_INCOMING_SEQUENCE_FLOWS"; protected static final String RECEIVE_TASK_WITH_ATTACHED_EVENT = "BPMN2_RECEIVE_TASK_WITH_ATTACHED_EVENT"; protected static final String GATEWAYDIRECTION_MIXED_FAILURE = "BPMN2_GATEWAYDIRECTION_MIXED_FAILURE"; protected static final String GATEWAYDIRECTION_CONVERGING_FAILURE = "BPMN2_GATEWAYDIRECTION_CONVERGING_FAILURE"; protected static final String GATEWAYDIRECTION_DIVERGING_FAILURE = "BPMN2_GATEWAYDIRECTION_DIVERGING_FAILURE"; protected static final String GATEWAY_WITH_NO_OUTGOING_SEQUENCE_FLOW = "BPMN2_GATEWAY_WITH_NO_OUTGOING_SEQUENCE_FLOW"; protected static final String GATEWAY_WITH_NO_INCOMING_SEQUENCE_FLOW = "BPMN2_GATEWAY_WITH_NO_INCOMING_SEQUENCE_FLOW"; protected static final String EVENT_SUBPROCESS_BAD_CONNECTION = "BPMN2_EVENT_SUBPROCESS_BAD_CONNECTION"; // CHOREOGRAPHY protected static final String TOO_MANY_INITIATING_MESSAGES = "BPMN2_TOO_MANY_INITIATING_MESSAGES"; protected static final String TOO_MANY_INITIATING_PARTICIPANTS = "BPMN2_TOO_MANY_INITIATING_PARTICIPANTS"; protected static final String TOO_FEW_INITIATING_PARTICIPANTS = "BPMN2_TOO_FEW_INITIATING_PARTICIPANTS"; private Definitions defs; // public HashSet<String> allowedNodes; // public HashSet<String> forbiddenNodes; public BPMN2SyntaxChecker(Definitions defs) { this.defs = defs; this.errors = new HashMap<String, String>(); // this.allowedNodes = new HashSet<String>(); // this.forbiddenNodes = new HashSet<String>(); } // @Override public boolean checkSyntax() { errors.clear(); this.checkEdges(); this.checkNodes(); return errors.size() == 0; } private void checkEdges() { for (Edge edge : this.defs.getEdges()) { if (edge.getSourceRef() == null) { this.addError(edge, NO_SOURCE); } else if (edge.getTargetRef() == null) { this.addError(edge, NO_TARGET); } else { if (edge instanceof MessageFlow) { FlowElement source = edge.getSourceRef(); FlowElement target = edge.getTargetRef(); /* * Not in same process or sub process */ if ((source.getProcess() != null && target.getProcess() != null && source .getProcess() == target.getProcess()) || (source.getSubProcess() != null && target.getSubProcess() != null && source .getSubProcess() == target.getSubProcess())) { this.addError(edge, SAME_PROCESS); } // if(edge.getSourceRef().getPool() == // edge.getTargetRef().getPool() && // edge.getSourceRef().getLane() != // edge.getTargetRef().getLane()) // this.addError(edge, MESSAGE_FLOW_NOT_ALLOWED); if (edge.getSourceRef() instanceof Lane || edge.getTargetRef() instanceof Lane) this.addError(edge, MESSAGE_FLOW_NOT_ALLOWED); } else if (edge instanceof SequenceFlow) { if (edge.getSourceRef().getProcess() != edge.getTargetRef() .getProcess()) this.addError(edge, DIFFERENT_PROCESS); } } } } private void checkNodes() { for (BaseElement rootElement : this.defs.getRootElement()) { /* * Checking of Regular BPMN2.0 Diagrams */ if (rootElement instanceof Process) { for (FlowElement flowElement : ((Process) rootElement) .getFlowElement()) { if (!(flowElement instanceof Edge)) { this.checkNode(flowElement); } } /* * Checking of Choroegraphy Diagrams */ } else if (rootElement instanceof Choreography) { for (FlowElement flowElement : ((Choreography) rootElement) .getFlowElement()) { if (!(flowElement instanceof Edge)) { this.checkChoreographyNode(flowElement); } } } else if(rootElement instanceof Collaboration) { (new BPMN2CollaborationChecker(this)).checkConversation((Collaboration)rootElement); } } } private void checkChoreographyNode(FlowElement flowElement) { this.checkNode(flowElement); if (flowElement instanceof ChoreographyActivity) { Integer instantiationMessageCounter = this .checkForInitiatingMessages((ChoreographyActivity) flowElement); Integer instantiatingCounter = 0; for (Participant participant : ((ChoreographyActivity) flowElement) .getParticipantRef()) { if (participant.isInitiating()) { instantiatingCounter++; if (instantiatingCounter > 1) this.addError(participant, TOO_MANY_INITIATING_PARTICIPANTS); } instantiationMessageCounter += this .checkForInitiatingMessages(participant); } if (instantiatingCounter == 0) this.addError(flowElement, TOO_FEW_INITIATING_PARTICIPANTS); if (instantiationMessageCounter > 1) this.addError(flowElement, TOO_MANY_INITIATING_MESSAGES); } } private Integer checkForInitiatingMessages(FlowElement flowElement) { Integer initiatingCounter = 0; // Check outgoing edges for (Edge outgoing : flowElement.getOutgoing()) if (outgoing.getTargetRef() instanceof Message) if (((Message) outgoing.getTargetRef()).isInitiating()) initiatingCounter++; // Check incoming edges for (Edge incoming : flowElement.getIncoming()) if (incoming.getSourceRef() instanceof Message) if (((Message) incoming.getSourceRef()).isInitiating()) initiatingCounter++; return initiatingCounter; } private void checkNode(FlowElement node) { // this.checkForAllowedAndForbiddenNodes(node); if ((node instanceof Activity || node instanceof Event || node instanceof Gateway) && node.getProcess() == null) { this.addError(node, FLOWOBJECT_NOT_CONTAINED_IN_PROCESS); } // Events if (node instanceof EndEvent && !this.hasIncomingControlFlow((FlowNode) node)) this.addError(node, ENDEVENT_WITHOUT_INCOMING_CONTROL_FLOW); if (node instanceof EndEvent && this.hasOutgoingControlFlow((FlowNode) node)) this.addError(node, ENDEVENT_WITH_OUTGOING_CONTROL_FLOW); if (node instanceof StartEvent && this.hasIncomingControlFlow((FlowNode) node)) this.addError(node, STARTEVENT_WITH_INCOMING_CONTROL_FLOW); if (node instanceof StartEvent && !this.hasOutgoingControlFlow((FlowNode) node)) this.addError(node, STARTEVENT_WITHOUT_OUTGOING_CONTROL_FLOW); if (node instanceof BoundaryEvent) this.checkBoundaryEvent((BoundaryEvent) node); // Gateways if (node instanceof Gateway) { this.checkGateway((Gateway) node); } // Data Objects if (node instanceof DataInput) for (Edge edge : ((DataInput) node).getIncoming()) if (edge instanceof DataInputAssociation || edge instanceof DataOutputAssociation) this.addError(node, DATA_INPUT_WITH_INCOMING_DATA_ASSOCIATION); if (node instanceof DataOutput) for (Edge edge : ((DataOutput) node).getOutgoing()) if (edge instanceof DataInputAssociation || edge instanceof DataOutputAssociation) this.addError(node, DATA_OUTPUT_WITH_OUTGOING_DATA_ASSOCIATION); // Subprocesses if (node instanceof SubProcess) this.checkSubProcess((SubProcess) node); } private void checkSubProcess(SubProcess node) { /* * this node is an event subprocess */ if (node.isTriggeredByEvent()) if (node.getIncomingSequenceFlows().size() > 0 || node.getOutgoingSequenceFlows().size() > 0) { // SPEC: P. 188 this.addError(node, EVENT_SUBPROCESS_BAD_CONNECTION); } } private void checkGateway(Gateway node) { /* * Eventbased Gateways can instantiate processes, thus we have to handle * them differently */ if (node instanceof EventBasedGateway) this.checkEventBasedGateway((EventBasedGateway) node); else // SPEC: P. 298 this.checkCommomGateway(node); } private void checkCommomGateway(Gateway node) { GatewayDirection direction = node.getGatewayDirection(); /* * gateways must have a minimum of one outgoing sequence flow */ if (node.getOutgoingSequenceFlows().size() == 0) { this.addError(node, GATEWAY_WITH_NO_OUTGOING_SEQUENCE_FLOW); } /* * gateways must have a minimum of one incoming sequence flow */ else if (!(node instanceof EventBasedGateway) && node.getIncomingSequenceFlows().size() == 0) { this.addError(node, GATEWAY_WITH_NO_INCOMING_SEQUENCE_FLOW); } /* * must have both multiple incoming and outgoing sequence flows */ else if (direction.equals(GatewayDirection.MIXED)) { if (node.getIncomingSequenceFlows().size() < 2 || node.getOutgoingSequenceFlows().size() < 2) { this.addError(node, GATEWAYDIRECTION_MIXED_FAILURE); } /* * must have multiple incoming sequence flows and must NOT have * multiple outgoing sequence flows */ } else if (direction.equals(GatewayDirection.CONVERGING)) { if (!(node.getIncomingSequenceFlows().size() > 1 && node .getOutgoingSequenceFlows().size() == 1)) { this.addError(node, GATEWAYDIRECTION_CONVERGING_FAILURE); } /* * must have multiple outgoing sequence flows and must NOT have * multiple incoming sequence flows */ } else if (direction.equals(GatewayDirection.DIVERGING)) { if (!(node.getIncomingSequenceFlows().size() == 1 && node .getOutgoingSequenceFlows().size() > 1)) { this.addError(node, GATEWAYDIRECTION_DIVERGING_FAILURE); } } } private void checkEventBasedGateway(EventBasedGateway node) { List<SequenceFlow> outEdges = node.getOutgoingSequenceFlows(); boolean receiveTaskFlag = false; boolean messageEventFlag = false; if (outEdges.size() < 2) // SPEC: P. 307 this.addError(node, EVENT_BASED_WITH_TOO_LESS_OUTGOING_SEQUENCE_FLOWS); if (node.isInstantiate() && !checkInstatiateCondition(node)) // SPEC: P. 309 this.addError(node, EVENT_BASED_NOT_INSTANTIATING); if (!node.isInstantiate() && node.getIncomingSequenceFlows().size() == 0) this.addError(node, EVENT_BASED_WITH_TOO_LESS_INCOMING_SEQUENCE_FLOWS); for (SequenceFlow edge : outEdges) { /* * outgoing sequence flows must NOT have a condition expression */ if (edge.getConditionExpression() != null) // SPEC: P. 308 // TODO: Look out for the correct implementation this.addError(edge, EVENT_BASED_WRONG_CONDITION_EXPRESSION); if (!(edge.getTargetRef() instanceof IntermediateCatchEvent || edge .getTargetRef() instanceof ReceiveTask)) { this.addError(node, EVENTBASEDGATEWAY_BADCONTINUATION); } else if (edge.getTargetRef() instanceof IntermediateCatchEvent) { if (((IntermediateCatchEvent) edge.getTargetRef()) .getEventDefinitionOfType(MessageEventDefinition.class) != null) messageEventFlag = true; /* Check for correct Events triggers */ if (!checkTrigger((IntermediateCatchEvent) edge.getTargetRef())) // SPEC: P. 308 this.addError(edge.getTargetRef(), EVENT_BASED_WRONG_TRIGGER); if (((IntermediateCatchEvent) edge.getTargetRef()) .getIncomingSequenceFlows().size() > 1) // SPEC: P. 308 this .addError(edge.getTargetRef(), EVENT_BASED_TARGET_WITH_TOO_MANY_INCOMING_SEQUENCE_FLOWS); } else if (edge.getTargetRef() instanceof ReceiveTask) { receiveTaskFlag = true; if (((ReceiveTask) edge.getTargetRef()) .getIncomingSequenceFlows().size() > 1) // SPEC: P. 308 this .addError(edge.getTargetRef(), EVENT_BASED_TARGET_WITH_TOO_MANY_INCOMING_SEQUENCE_FLOWS); if (((ReceiveTask) edge.getTargetRef()).getBoundaryEventRefs() .size() != 0) // SPEC: P. 308 this.addError(edge.getTargetRef(), RECEIVE_TASK_WITH_ATTACHED_EVENT); } } if (receiveTaskFlag && messageEventFlag) // SPEC: P. 308 this.addError(node, EVENT_BASED_EVENT_TARGET_CONTRADICTION); } // TODO: Check if this Method and the invoked one are really necessary // private void checkForAllowedAndForbiddenNodes(FlowElement node) { // // Check for allowed and permitted nodes // if(!checkForAllowedNode(node, allowedNodes, true) || // !checkForAllowedNode(node, forbiddenNodes, false)){ // System.out.println("error"); // addError(node, NODE_NOT_ALLOWED); // } // } // // private boolean checkForAllowedNode(FlowElement node, HashSet<String> // classes, boolean allowed) { // // If checking for allowed classes, empty classes means all are allowed // if(allowed && classes.size() == 0) // return true; // // boolean containedInClasses = false; // String nodeClassName = node.getClass().getSimpleName(); // // for(String clazz : classes){ // //TODO this doesn't checks for superclasses!!! // // better would be "node instanceof Class.forName(clazz)" // if(clazz.equals(nodeClassName)){ // containedInClasses = true; // } else if(clazz.equals("MultipleInstanceActivity")){ // containedInClasses = (node instanceof Activity) && // ((Activity)node).isMultipleInstance(); // } // // if(containedInClasses) break; // } // // return containedInClasses == allowed; // } private boolean checkInstatiateCondition(EventBasedGateway node) { boolean hasStartEvent = false; for (FlowElement flowElement : node.getProcess().getFlowElement()) { if (flowElement instanceof StartEvent) hasStartEvent = true; } /* * if the process has no start events and the gateway has no incoming * sequence flows it instantiates the process */ if (!hasStartEvent && node.getIncomingSequenceFlows().size() == 0) return true; /* OR */ /* * the gateway has an incoming sequence flow but its source is a none * start event */ if (node.getIncomingSequenceFlows().size() == 1) { for (SequenceFlow sf : node.getIncomingSequenceFlows()) { if (sf.getSourceRef() instanceof StartEvent) { return true; } } } return false; } private boolean checkTrigger(IntermediateCatchEvent event) { // TODO: Add support multiple if (event.getEventDefinitionOfType(MessageEventDefinition.class) != null || event.getEventDefinitionOfType(TimerEventDefinition.class) != null || event.getEventDefinitionOfType(SignalEventDefinition.class) != null || event .getEventDefinitionOfType(ConditionalEventDefinition.class) != null) { return true; } return false; } private void checkBoundaryEvent(BoundaryEvent node) { if (this.hasIncomingControlFlow(node)) this.addError(node, ATTACHEDINTERMEDIATEEVENT_WITH_INCOMING_CONTROL_FLOW); if (node.getOutgoingSequenceFlows().size() != 1 && node .getEventDefinitionOfType(CompensateEventDefinition.class) == null) this.addError(node, ATTACHEDINTERMEDIATEEVENT_WITHOUT_OUTGOING_CONTROL_FLOW); } private boolean hasIncomingControlFlow(FlowNode node) { return node.getIncomingSequenceFlows().size() > 0; } private boolean hasOutgoingControlFlow(FlowNode node) { return node.getOutgoingSequenceFlows().size() > 0; } /** * Checks whether the {@link FlowElement} has a correlating diagram element. * * @param element * * @return * true if diagram element exists */ private boolean hasCorelatingDiagramElement(FlowElement element) { for(DiagramElement diaEl : defs.getFirstPlane().getDiagramElement()) { if(diaEl instanceof BPMNShape && ((BPMNShape) diaEl).getBpmnElement().equals(element)) { return true; } if(diaEl instanceof BPMNEdge && ((BPMNEdge) diaEl).getBpmnElement().equals(element)) { return true; } } return false; } protected void addError(FlowElement elem, String errorText) { if(hasCorelatingDiagramElement(elem)) { this.errors.put(elem.getId(), errorText); } } }